/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2013-2019.
 * Description: fiq_glue_setup.c
 * Author: xiekunxun <xiekunxun@huawei.com>
 * Create: 2013-04-07
 */
#ifdef CONFIG_RTOS_HAL_FIQ_ARM_WITH_TF
#include <linux/arm-smccc.h>
#endif
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/cache.h>
#include <asm/io.h>
#include <asm/cache.h>
#include "../../fiq_glue_com/fiq_glue_private.h"

struct fiq_param64 {
	uint64_t a0;
	uint64_t a1;
	uint64_t a2;
	uint64_t a3;
	uint64_t a4;
	uint64_t a5;
	uint64_t a6;
};

uint64_t *nmi_virt_dfx_base;

#ifdef CONFIG_RTOS_HAL_FIQ_SUPPORT_RESERVED_MEMORY
static unsigned long fiq_ctx_start, fiq_ctx_size;
static int __init fiq_early_init(char *p)
{
	unsigned long start;
	unsigned long size;
	char *endp = NULL;

	start = memparse(p, &endp);
	if (*endp == ',') {
		size = memparse(endp + 1, NULL);

		fiq_ctx_start = start;
		fiq_ctx_size = size;
	}
	return 0;
}
early_param("fiqctx", fiq_early_init);
#endif

#ifdef CONFIG_RTOS_HAL_FIQ_GLUE_GUESTOS
static bool fiq_guestos;

static int __init fiq_guestos_init(char *__unused)
{
	fiq_guestos = 1;
	return 0;
}
early_param("fiq_guestos", fiq_guestos_init);
#endif

static noinline int fiq_smccc_call(uint64_t a0, uint64_t a1, uint64_t a2)
{
#ifdef CONFIG_RTOS_HAL_FIQ_GLUE_GUESTOS
	if (fiq_guestos) {
		struct arm_smccc_res res;
		arm_smccc_hvc(a0, a1, a2, 0, 0, 0, 0, 0, &res);
		return res.a0;
	}
#endif
#ifdef CONFIG_RTOS_HAL_FIQ_ARM_WITH_TF
	struct arm_smccc_res res;
	arm_smccc_smc((uint32_t)a0, (uint32_t)a1, (uint32_t)a2, 0, 0, 0, 0, 0, &res);
	return res.a0;
#else
	asm volatile("mov x0, %0"::"r"(a0));
	asm volatile("mov x1, %0"::"r"(a1));
	asm volatile("mov x2, %0"::"r"(a2));
	asm volatile("smc #0");
	return 0;
#endif
}

static int32_t tf_register_nmi_interrupt(uint64_t *nmi_interrupt_handler,
								uint64_t *ctx_vaddr)
{
	struct fiq_param64 param = {0};

	param.a0 = SMC_FIQ_GLUE_ID;
	param.a1 = (uint64_t)((uintptr_t)nmi_interrupt_handler);
	param.a2 = (uint64_t)((uintptr_t)ctx_vaddr);
	pr_info("[RTOS fiq]smc register: a0:%llx, a1:%pK, a2:%pK\n",
		param.a0, (void *)((uintptr_t)param.a1), (void *)((uintptr_t)param.a2));
	fiq_smccc_call(param.a0, param.a1, param.a2);
	return 0;
}

uint32_t tf_restore_context(void)
{
	struct fiq_param64 param = {0};

	param.a0 = SMC_FIQ_GLUE_RETURN_CODE;
	printk("[%s] [%d]\n", __func__, __LINE__);
	fiq_smccc_call(param.a0, param.a1, param.a2);
	return 0;
}

#ifdef CONFIG_RTOS_HAL_FIQ_SUPPORT_RESERVED_MEMORY
static int __fiq_glue_register_handler(struct fiq_glue_handler *handler)
{
	void __iomem *virt_base = NULL;
	unsigned int cpus = nr_cpu_ids;
	unsigned long nmi_ctx_size;

	if (!fiq_ctx_start)
		return -EINVAL;
	/* if cores count is less than or equal to 32, we keep it as 32 forcely for compatibility. if cores count
	 * is more than 32, NMI_CTX_SP_SIZE bytes reserved memory is required for fiq register context.
	 */
	if (cpus <= 32) {
		cpus = 32;
		nmi_ctx_size = cpus * FIQ_GLUE_REG_BUF_SIZE;
	} else {
		nmi_ctx_size = NMI_CTX_SP_SIZE;
	}

	if (fiq_ctx_size < nmi_ctx_size + NMI_CTX_BUFFER_DFX_SIZE) {
		pr_err("[RTOS fiq] FIQ reserved mem size 0x%lx < 0x%lx\n",
			fiq_ctx_size, nmi_ctx_size + NMI_CTX_BUFFER_DFX_SIZE);
		return -EINVAL;
	}

	virt_base = ioremap_cache(fiq_ctx_start, fiq_ctx_size);
	if (!virt_base)
		return -ENOMEM;

	memset(virt_base, 0, fiq_ctx_size);
	nmi_virt_dfx_base = (uint64_t *)((char *)virt_base + nmi_ctx_size);
	pr_info("[RTOS fiq] FIQ register mem:%pK, FIQ dfx addr: %pK", (void *)virt_base, (void *)nmi_virt_dfx_base);
	pr_info("[RTOS fiq] FIQ reserved memory for reigsters: %pK\n", (void *)fiq_ctx_start);

	tf_register_nmi_interrupt((uint64_t *)fiq_el1_trigger, (uint64_t *)(uintptr_t)fiq_ctx_start);
	fiq_glue_reg((uint64_t *)virt_base);
	return 0;
}
#endif

char __cacheline_aligned nmi_virt_ctx_base[NMI_CTX_SP_SIZE + NMI_CTX_BUFFER_DFX_SIZE];

int fiq_glue_register_handler(struct fiq_glue_handler *handler)
{
	uint64_t *nmi_phys_ctx_base = NULL;

#ifdef CONFIG_RTOS_HAL_FIQ_SUPPORT_RESERVED_MEMORY
	if (!__fiq_glue_register_handler(handler))
		return 0;
#endif

	memset(nmi_virt_ctx_base, 0, NMI_CTX_SP_SIZE + NMI_CTX_BUFFER_DFX_SIZE);

	nmi_virt_dfx_base = (uint64_t *)((char *)nmi_virt_ctx_base + NMI_CTX_SP_SIZE);
	printk(KERN_INFO"[RTOS fiq] FIQ register mem:%pK, FIQ dfx addr: %pK", (void *)nmi_virt_ctx_base, (void *)nmi_virt_dfx_base);
	nmi_phys_ctx_base = (uint64_t *)virt_to_phys((void *) nmi_virt_ctx_base);
	printk(KERN_INFO"[RTOS fiq] FIQ reserved memory for reigsters: %pK\n", (void *)nmi_phys_ctx_base);

	tf_register_nmi_interrupt((uint64_t *)fiq_el1_trigger, (uint64_t *)nmi_phys_ctx_base);
	fiq_glue_reg((uint64_t *)nmi_virt_ctx_base);

	return 0;
}

